<?php

namespace pay;

use think\facade\Log;

/**
 * 杉德直连支付类
 */
class ShanDeDirect
{
    private $mid; // 商户号
    private $pubKey; // 商户公钥
    private $priKey; // 商户私钥
    private $priKey_path; // 商户私钥地址
    private $private_pass; // 商户私钥
    private $request_url; // 请求地址

    /**
     * 构造方法
     * @param [type] $mid           商户号
     * @param [type] $private_path  商户私钥路径
     * @param [type] $public_path   商户公钥路径
     * @param [type] $private_pass  商户私钥密码
     * @param string $request_url   请求地址
     */
    public function __construct($mid, $private_path, $public_path, $private_pass, $request_url='https://cap.sandpay.com.cn')
    {
        $public_address = root_path()."public/"; // public 地址
        $this->priKey_path = $public_address.$private_path;
        $this->pubKey = EncryptHelp::loadX509Cert($public_address.$public_path);  // 获取公钥
        $this->priKey = EncryptHelp::loadPk12Cert($this->priKey_path, $private_pass);  // 获取私钥
        
        $this->mid = $mid; // 商户号
        $this->request_url = $request_url; // 请求地址
        $this->private_pass = $private_pass; // 商户私钥密码
    }

    /**
     * 获取公共头数据
     * @return void
     */
    private function get_public_data() 
    {
        return array(
            'version'           =>  '1.0',
            'mid'               =>  $this->mid, // 商户号
            'customerOrderNo'   =>  mt_rand(111111111100000,999999999999999),  //  商户订单号
            'signType'          =>  'SHA1WithRSA',  //  签名方式
            'encryptType'       =>  'AES',  // 	加密方式
            'timestamp'         =>  date('Y-m-d H:i:s'),    //  时间戳
        );
    }

    /**
     * 签名并请求
     * @param [type] $url   请求地址
     * @param [type] $params    请求参数
     * @return void
     */
    private function public_sign_request($url, $params)
    {
        $public_data = $this->get_public_data();
        // step1: 拼接报文
        $data = array_merge($public_data, $params);
        // step2: 生成AESKey并使用公钥加密 先生成16位随机字符串aesKey，并且转成aesKeyBytes字节数组
        $AESKey = EncryptHelp::aes_generate(16);
        // step3: 使用AESKey加密报文 通过aesKeyBytes对Json进行AES加密生成data
        $data['data'] = EncryptHelp::AESEncrypt($data, $AESKey);
        // step4: 把aesKeyBytes通过杉德公钥加密生成encryptKey，encryptType为"AES"
        $data['encryptKey'] = EncryptHelp::RSAEncryptByPub($AESKey, $this->pubKey);
        // step5: 将加密后的data，通过商户私钥进行签名生成sign，signType为"SHA1WithRSA"
        $data['sign'] = EncryptHelp::sign($data['data'], $this->priKey);
        // step7: post请求
        Log::channel('request')->info(sprintf("通道请求数据: \n%s", var_export($data, true)));    // 记录日志
        $result = EncryptHelp::http_post_json($this->request_url.$url, $data);
        // echo var_export(json_encode($data));
        $arr= json_decode($result, true);
        try {
            // step8: 使用公钥验签报文$decryptPlainText
            $verify = EncryptHelp::verify($arr['data'], $arr['sign'], $this->pubKey);
            // step9: 使用私钥解密AESKey
            $decryptAESKey = EncryptHelp::RSADecryptByPri($arr['encryptKey'], $this->priKey);
            // step10: 使用解密后的AESKey解密报文
            $decryptPlainText = EncryptHelp::AESDecrypt($arr['data'], $decryptAESKey);
            $return_data = ['code'=> 0, 'message'=> '操作成功', 'body'=>  $decryptPlainText];
            Log::channel('request')->info(sprintf("通道返回数据: \n%s", var_export($decryptPlainText, true)));    // 记录日志
        } catch (\Exception $e) {
            $return_data = ['code'=> 100, 'message'=> $e->getMessage(), 'body'=> []];
        }
        return $return_data;
    }

    /**
     * 支付签名
     * @param [type] $str
     * @param [type] $priKey
     * @return void
     */
    private function sign($str) 
    {
        $file = file_get_contents($this->priKey_path);
        if (!$file) {
            throw new \Exception('loadPk12Cert::file_get_contents');
        }
        if (!openssl_pkcs12_read($file, $cert, $this->private_pass)) {
            throw new \Exception('loadPk12Cert::openssl_pkcs12_read ERROR');
        }
        $pem = $cert['pkey'];
        openssl_sign($str, $sign, $pem);
        $sign = base64_encode($sign);
        return $sign;
    }

    /**
     * 查询会员状态
     * @param [type] $bizUserNo 会员编号
     * @return void
     */
    function findNumberInfo($bizUserNo)
    {
        // 请求数据且获取结果
        $result = $this->public_sign_request("/v4/elecaccount/ceas.elec.member.info.query", array("bizUserNo"=> $bizUserNo));
        $body = json_decode($result["body"], true);
        $data = ["code"=> 500, "message"=> $body["responseDesc"]];
        // 请求成功
        if ($body["responseStatus"] == "00"){
            $data = ["code"=> 0, "data"=> ['name' => $body["name"], 'idNo' => $body["idNo"], 'phone' => $body["mobile"], 'open' => $body["openAccountStatus"] == '01' ? true : false]];
        } elseif ($body["responseStatus"] == "01"){
            // 未注册账号
            if($body["responseCode"] == '05017'){
                $data = ["code"=> 100, "message"=> "未开户!"];
            }
        }
        return $data;
    }

    /**
     * 查询会员状态
     * @param [type] $bizUserNo 会员编号
     * @return void
     */
    function findNumberStatus($bizUserNo)
    {
        // 请求数据且获取结果
        $result = $this->public_sign_request("/v4/elecaccount/ceas.elec.member.status.query", array("bizUserNo"=> $bizUserNo));
        $body = json_decode($result["body"], true);

        $data = ["code"=> 500, "message"=> $body["responseDesc"]];
        // 请求成功
        if ($body["responseStatus"] == "00"){
            // 会员状态
            switch($body["memberStatus"]){
                case "00": // 正常
                    $data = ["code"=> 0, "message"=> "会员状态正常", "data"=> $body];
                    break;
                case "01": // 冻结
                    $data = ["code"=> 200, "message"=> "会员冻结"];
                    break;
                case "02": // 未激活
                    $data = ["code"=> 400, "message"=> "会员未激活"];      
                    break;
                case "09": // 销户
                    $data = ["code"=> 0, "message"=> "会员已销户, 通过此链接前往重新开户!"];      
                    break;
                case "11": // 风控冻结
                    $data = ["code"=> 200, "message"=> "会员风控冻结"];      
                    break;
                default:
                    $data = ["code"=> 500, "message"=> "状态异常", "data"=> "未知的状态码"];
                    break;
            }
        } elseif ($body["responseStatus"] == "01"){
            // 未注册账号
            if($body["responseCode"] == '05017'){
                $data = ["code"=> 0, "message"=> "通过此链接前往开户!"];
            }
        }
        return $data;
    }

    /**
     * 会员销户
     * @param [type] $bizUserNo     // 会员编号
     * @param [type] $notifyUrl     // 异步通知地址
     * @param [type] $frontUrl      // 同步回调
     * @param [type] $remark        // 备注
     * @param string $bizType       // 业务类型 CLOSE－销户 默认为销户（为之后冻结解冻功能预留） FREEZE-冻结会员 UNFREEZE-解冻会员
     * @return void
     */
    function NumberStatusManage($bizUserNo, $notifyUrl, $frontUrl, $remark, $bizType='CLOSE') 
    {
        // 请求数据且获取结果
        $result = $this->public_sign_request("/v4/elecaccount/ceas.elec.account.member.status.modify", array(
            "bizUserNo"=> $bizUserNo,   //  会员编号
            "bizType"=> $bizType,       //  操作类型  
            "notifyUrl"=> $notifyUrl,   // 异步通知地址
            "frontUrl"=> $frontUrl,     // 前台跳转地址
            "remark"=> $remark         // 备注
        ));
        $body = json_decode($result["body"], true);
        $data = ["code"=> 500, "message"=> $body["responseDesc"]];
        // 请求成功
        if ($body["responseCode"] == "00000"){
            // 操作状态
            if ($body["responseStatus"] != "00"){
                $data = array_replace($data, ["code"=> 200, "message"=> $body["responseDesc"]]);
            }else{
                // 会员状态
                switch($body["authWay"]){
                    case "01":
                        $authWay_name = "发送短信（有效时间5分钟）";
                        break;
                    case "02":
                        $authWay_name = "支付密码";
                        break;
                    case "03":
                        $authWay_name = "免密免短信（无需确认支付）";
                        break;
                    case "04":
                        $authWay_name = "支付密码+短信";      
                        break;
                    default:
                        $authWay_name = "其他方式";
                        break;
                }
                $data = array_replace($data, ["code"=> 0, "authWay"=> $authWay_name, "sandSerialNo"=> $body["customerOrderNo"]]); 
            }
        }
        return $data;
    }

    /**
     * 会员管理确认
     * @param [type] $bizUserNo             会员编号
     * @param [type] $oriCustomerOrderNo    原交易订单号
     * @param [type] $smsCode               短信验证码
     * @return void
     */
    function NumberStatusManageConfirm($bizUserNo, $oriCustomerOrderNo, $smsCode) 
    {
        // 请求数据且获取结果
        $result = $this->public_sign_request("/v4/elecaccount/ceas.elec.account.member.modify.confirm", array(
            "bizUserNo"=> $bizUserNo,
            "oriCustomerOrderNo"=> $oriCustomerOrderNo,
            "smsCode"=> $smsCode
        ));
        $body = json_decode($result["body"], true);
        $data = ["code"=> 200, "message"=> $body["responseDesc"]];
        // 请求成功
        if ($body["responseCode"] == "00000"){
            // 操作状态
            if ($body["responseStatus"] != "00"){
                $data = array_replace($data, ["code"=> 200, "message"=> $body["responseDesc"]]);
            }else{
                $data = array_replace($data, ["code"=> 0, "message"=> '操作成功']); 
            }
        }
        return $data;
    }

    /**
     * 查询钱包余额
     * @param [type] $bizUserNo 会员编号
     * @return void
     */
    private function findNumberWalletBalance($bizUserNo, $money) 
    {
        // 请求数据且获取结果
        $result = $this->public_sign_request("/v4/elecaccount/ceas.elec.account.balance.query", array("bizUserNo"=> $bizUserNo));
        $body = json_decode($result["body"], true);
        $data = ["code"=> 500, "message"=> $body["responseDesc"], "body"=> 0];
        // 请求成功
        if ($body["responseStatus"] == "00"){
            $balance_array = []; // 钱包数组
            // 余额
            foreach($body["accountList"] as $v){
                switch($v["accountType"]){
                    case "01": // 支付电子户
                        $balance_array["payment_account"] = $v;
                        break;
                    case "02": // 权益账户
                        $balance_array["Equity_account"] = $v;
                        break;
                    case "03": // 奖励金账户
                        $balance_array["reward_account"] = $v;
                        break;
                    case "04": // 保证金户
                        $balance_array["margin_account"] = $v;
                        break;
                    default:
                        // 异常账户
                        break;
                }
            }
            $data = ["code"=> 0, "message"=> '操作正常', "body"=> $balance_array];
            // 判断余额是否足够
            if ($balance_array["payment_account"]["availableBal"] < $money){
                $data = ["code"=> 200, "message"=> '余额不足, 通过此链接前往充值钱包！', "body"=> $balance_array];
            }
        }
        return $data;
    }

    /**
     *  获取支付链接
     * @param [type] $order_no
     * @param [type] $money
     * @param [type] $user_id
     * @param [type] $nickname
     * @param [type] $ip
     * @param string $goods_name
     * @return void
     */
    private function get_pay_url($order_no, $money, $user_id, $nickname, $ip, $goods_name='皮肤', $product_code='04010001', $return_url=""){
        $notify_url = $product_code == '04010001'? 'https://pay.gogoskins.com/api/payNotify': 'https://pay.gogoskins.com/api/userNotify';
        $data = [
            'version' => 10,
            'mer_no' =>  $this->mid, //商户号
            'mer_order_no' => $order_no, //商户唯一订单号
            'create_time' => date('YmdHis'),
            'expire_time' => date('YmdHis', time()+ 30 * 60),
            'order_amt' =>  $money, //订单支付金额
            'notify_url' => $notify_url, //订单支付异步通知
            'return_url' => $return_url, //订单前端页面跳转地址
            'create_ip' => $ip,
            'goods_name' => $goods_name,
            'store_id' => '000000', // 门店编号
            'product_code' => $product_code,   // 支付协议
            'clear_cycle' => '3',   // 清算模式
            'pay_extra' => json_encode(["userId"=>$user_id, "nickName"=> $nickname, "accountType"=>"1"]),  // 支付扩展字段  "nickName"："昵称|姓名|身份证|手机号" 
            'accsplit_flag' => 'NO',    // 分账标识
            'jump_scheme' => '', // 跳转scheme
            'meta_option' => json_encode([["s" => "web","n" => "BankCard","id" => "com.pay.paytype","sc" => "com.pay.paytype"]]), // 终端/网站参数
            'sign_type' => 'RSA'
        ];
        $temp = $data;
        unset($temp['goods_name']);
        unset($temp['jump_scheme']);
        unset($temp['expire_time']);
        unset($temp['product_code']);
        unset($temp['clear_cycle']);
        unset($temp['meta_option']);
        
        $sign = $this->sign(EncryptHelp::getSignContent($temp));
        $data['sign'] = $sign;
        $query = http_build_query($data) ;
        $payurl = "https://faspay-oss.sandpay.com.cn/pay/h5/cloud?".$query;  // 电子钱包【云账户】：cloud
        return $payurl; // 返回支付url
    }

    /**
     * 调起支付
     * @param [type] $order_no      订单号
     * @param [type] $money         金额
     * @param [type] $ip            ip
     * @param [type] $goods_name    商品名字
     * @param [type] $user_id       用户user_id
     * @param [type] $nickname      昵称组合
     * @param boolean $is_register  是否注册
     * @return void
     */
    function pay($order_no, $money, $ip, $goods_name, $user_id, $nickname, $is_register=false, $return_url=false) {
        // 返回支付数组
        $result = ["code"=> 0, "message" => "通过该链接前往支付!", "pay_url"=> ""];

        // // 未注册查询会员状态
        // $status_data = $this->findNumberStatus($user_id);
        // $result = array_merge($result, $status_data);
        
        if($is_register){
            // 钱包余额
            $wallet_data = $this->findNumberWalletBalance($user_id, $money);
            $result = array_merge($result, $wallet_data);
        }
        // 获取支付链接
        $result["pay_url"] = $this->get_pay_url($order_no, $money, $user_id, $nickname, $ip, $goods_name, !$is_register ? "00000001" : "04010001", $return_url);

        return $result;
    }
}